/*
 * Copyright (c) 2025 HiEasyX
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * \file hex_impl_easyx.cpp
 * \brief The HiEasyX impl of the EasyX
 */

#include <algorithm>
#include <include/impl/EasyX/hex_impl_easyx.h>

#include <graphics.h>
#include <iterator>

#pragma comment(lib, "MSIMG32.LIB")

namespace HX {
void Begin(HXContext *RenderContext);

void OSAPI(HXOSOperation *API);

void HXBegin() {
	static HXContextImpl     context;
	static HXOSOperationImpl api;
	Begin(&context);
	OSAPI(&api);
}

void MessageSender(HXMessageSender *Sender);

void CreateTheme();

void HXInitForEasyX() {
	HXMessageSenderImpl *sender = new HXMessageSenderImpl;
	MessageSender(sender);

	CreateTheme();
}

void *GetHXBuffer(IMAGE *Buffer) {
	return static_cast<void *>(Buffer);
}
}

#define HXColorToEasyXColor(COLOR) (RGB(COLOR.R, COLOR.G, COLOR.B))

/////////////////////////////////////////////
/// HXBufferPainterImpl

HXBufferPainterImpl::HXBufferPainterImpl(IMAGE *Buffer) {
	_buffer = Buffer;

	setbkmode(TRANSPARENT);
}

void HXBufferPainterImpl::DrawLine(HXPoint Point1, HXPoint Point2, HXColor Color) {
	setlinecolor(HXColorToEasyXColor(Color));

	line(Point1.X, Point1.Y, Point2.X, Point2.Y);
}

void HXBufferPainterImpl::DrawLineStyled(HXPoint Point1, HXPoint Point2, HXColor Color, HXLineStyle Style) {
	LINESTYLE style;
	LINESTYLE oldStyle;
	getlinestyle(&style);

	oldStyle = style;

	style.thickness = Style.Thickness;
	switch (Style.Shape) {
	case HXLineShapeStyle::Dotted: {
		style.style = PS_DOT;
		break;
	}
	case HXLineShapeStyle::Dash: {
		style.style = PS_DASH;
		break;
	}
	case HXLineShapeStyle::DashDotted: {
		style.style = PS_DASHDOT;
		break;
	}
	default: {
		style.style = PS_SOLID;
		break;
	}
	}

	setlinestyle(&style);
	setlinecolor(HXColorToEasyXColor(Color));

	line(Point1.X, Point1.Y, Point2.X, Point2.Y);

	setlinestyle(&oldStyle);
}

void HXBufferPainterImpl::DrawRectangle(HXRect Rect, HXColor Color) {
	setlinecolor(HXColorToEasyXColor(Color));

	rectangle(Rect.Left, Rect.Top, Rect.Right, Rect.Bottom);
}

void HXBufferPainterImpl::DrawFilledRectangle(HXRect Rect, HXColor Color, HXColor FillColor) {
	setlinecolor(HXColorToEasyXColor(Color));
	setfillcolor(HXColorToEasyXColor(FillColor));

	solidrectangle(Rect.Left, Rect.Top, Rect.Right, Rect.Bottom);
	rectangle(Rect.Left, Rect.Top, Rect.Right, Rect.Bottom);
}

void HXBufferPainterImpl::DrawImage(void *Buffer, HXPoint Where, HXPoint Size) {
	const auto image  = static_cast<IMAGE *>(Buffer);
	const HDC  target = GetImageHDC(GetWorkingImage());
	const HDC  source = GetImageHDC(image);

	constexpr BLENDFUNCTION blendFunction = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};

	AlphaBlend(target, Where.X, Where.Y, Size.X, Size.Y, source, 0, 0,
	           image->getwidth(), image->getheight(), blendFunction);
}

HXPoint HXBufferPainterImpl::MeasureImage(void *Buffer) {
	const auto image = static_cast<IMAGE *>(Buffer);

	return {image->getwidth(), image->getheight()};
}

void HXBufferPainterImpl::DrawPainter(HXBufferPainter *Painter, HXPoint Where) {
	putimage(Where.X, Where.Y, static_cast<HXBufferPainterImpl *>(Painter)->_buffer);
}

void HXBufferPainterImpl::DrawPainter(HXBufferPainter *Painter, HXPoint Where, HXPoint Origin, HXPoint Size) {
	putimage(Where.X, Where.Y, Size.X, Size.Y, static_cast<HXBufferPainterImpl *>(Painter)->_buffer, Origin.X,
	         Origin.Y);
}

void HXBufferPainterImpl::SetupEasyXFont(HXFont Font, HXGUInt Height) {
	LOGFONT font;

	gettextstyle(&font);
	_tcscpy_s(font.lfFaceName, Font.Family.c_str());
	font.lfItalic  = Font.Italic;
	font.lfQuality = PROOF_QUALITY;

	switch (Font.Style) {
	case HXFontStyle::Regular:
		font.lfWeight = FW_NORMAL;
		break;
	case HXFontStyle::Bold:
		font.lfWeight = FW_BOLD;
		break;
	case HXFontStyle::Black:
		font.lfWeight = FW_BLACK;
		break;
	case HXFontStyle::Light:
		font.lfWeight = FW_LIGHT;
		break;
	case HXFontStyle::Medium:
		font.lfWeight = FW_MEDIUM;

	// Just for fallback
	default:
		font.lfWeight = FW_NORMAL;
		break;
	}
	font.lfHeight = static_cast<LONG>(Height);

	settextstyle(&font);
}

void HXBufferPainterImpl::DrawText(const HXString &Text, HXFont Font, HXPoint Where, HXColor Color, HXGUInt Height) {
	SetupEasyXFont(Font, Height);

	settextcolor(HXColorToEasyXColor(Color));

	outtextxy(Where.X, Where.Y, Text.c_str());
}

void HXBufferPainterImpl::DrawFilledPolygon(std::vector<HXPoint> Points, HXColor Color, HXColor FillColor) {
	setfillcolor(HXColorToEasyXColor(FillColor));
	setlinecolor(HXColorToEasyXColor(Color));

	std::vector<POINT> points;
	points.reserve(Points.size());
	for (auto &point : Points) {
		points.emplace_back(POINT{static_cast<LONG>(point.X), static_cast<LONG>(point.Y)});
	}

	fillpolygon(points.data(), points.size());
}

void HXBufferPainterImpl::DrawFilledRoundedRectangle(HXRect Rect, HXColor Color, HXColor FillColor, HXGInt Radius) {
	setlinecolor(HXColorToEasyXColor(Color));
	setfillcolor(HXColorToEasyXColor(FillColor));

	fillroundrect(Rect.Left, Rect.Top, Rect.Right, Rect.Bottom, Radius, Radius);
}

HXRect HXBufferPainterImpl::MeasureText(const HXString &Text, HXFont Font, HXGUInt Height) {
	SetupEasyXFont(Font, Height);

	return {0, 0, textwidth(Text.c_str()), textheight(Text.c_str())};
}

HXRect HXBufferPainterImpl::MeasureText(const HXChar &Text, HXFont Font, HXGUInt Height) {
	SetupEasyXFont(Font, Height);

	return {0, 0, textwidth(Text), textheight(Text)};
}

void HXBufferPainterImpl::Clear(HXColor Color) {
	setbkcolor(HXColorToEasyXColor(Color));

	cleardevice();
}

HXBufferPainter *HXBufferPainterImpl::CreateSubPainter(HXGInt Width, HXGInt Height) {
	return new HXExHostedBufferPainterImpl(Width, Height);
}

HXBufferPainter *HXBufferPainterImpl::CreateFromBuffer(void *Buffer) {
	return new HXBufferPainterImpl(static_cast<IMAGE *>(Buffer));
}

/**
 * The node to store the painter list
 */
struct PainterNode {
	HXBufferPainterImpl *painter = nullptr;
	PainterNode *        next    = nullptr;
	PainterNode *        last    = nullptr;
};

void HXBufferPainterImpl::Begin() {
	SetWorkingImage(_buffer);

	setbkmode(TRANSPARENT);
}

void HXBufferPainterImpl::End() {
	SetWorkingImage();
}

/////////////////////////////////////////////
/// HXExHostedBufferPainterImpl

HXExHostedBufferPainterImpl::HXExHostedBufferPainterImpl(HXGInt Width, HXGInt Height)
	: HXBufferPainterImpl(new IMAGE(Width, Height)) {
}

HXExHostedBufferPainterImpl::~HXExHostedBufferPainterImpl() {
	delete _buffer;
}

/////////////////////////////////////////////
/// HXContextImpl

HXContextImpl::HXContextImpl() {
	_defaultPainter = new HXBufferPainterImpl(nullptr);
}

HXBufferPainter *HXContextImpl::DefaultPainter() {
	return _defaultPainter;
}

HXBufferPainter *HXContextImpl::BufferToPainter(void *Buffer) {
	return new HXBufferPainterImpl(static_cast<IMAGE *>(Buffer));
}

HXBuffer *HXContextImpl::GetDeviceBuffer() {
	return reinterpret_cast<HXBuffer *>(GetImageBuffer());
}

/////////////////////////////////////////////
/// HXMessageSenderImpl

HXMessage HXMessageSenderImpl::Message(void *Message) {
	HXMessage message{};
	auto      exMessage = static_cast<ExMessage *>(Message);
	if (exMessage->message == WM_LBUTTONDOWN || exMessage->message == WM_LBUTTONUP) {
		message.MouseAction = true;
	}
	if (exMessage->message == WM_MOUSEMOVE) {
		message.MouseAction = true;
	}
	if (exMessage->message == WM_LBUTTONDOWN) {
		message.MouseLeftPressed = true;
	}
	if (exMessage->message == WM_LBUTTONUP) {
		message.MouseLeftRelease = true;
	}
	if (exMessage->message == WM_KEYDOWN) {
		message.KeyPressed = true;
		message.KeyCode    = exMessage->vkcode;
	}
	if (exMessage->message == WM_KEYUP) {
		message.KeyReleased = true;
		message.KeyCode     = exMessage->vkcode;
	}
	if (exMessage->message == WM_CHAR) {
		message.InputChar = true;
		message.Char      = exMessage->ch;
	}
	if (exMessage->message == WM_MOUSEWHEEL) {
		message.MouseAction  = true;
		message.MouseWheel   = true;
		message.MouseWheelUp = exMessage->wheel < 0;
	}
	if (message.MouseAction) {
		message.MouseX = exMessage->x;
		message.MouseY = exMessage->y;
	}

	message.InShift = GetKeyState(VK_SHIFT) & 0x8000 ? true : false;

	return message;
}

////////////////////////////////////////////
/// Global

namespace HX {
void *GetHXMessage(ExMessage *Message) {
	return static_cast<void *>(Message);
}
}

void HXOSOperationImpl::SetCursorStyle(HXCursorStyle Style) {
	switch (Style) {
	case HXCursorStyle::Normal: {
		LPCTSTR cursor       = IDC_ARROW;
		auto    cursorHandle = LoadCursor(NULL, cursor);
		SetClassLongPtr(GetHWnd(), GCLP_HCURSOR, reinterpret_cast<LPARAM>(cursorHandle));

		break;
	}
	case HXCursorStyle::Editing: {
		LPCTSTR cursor       = IDC_IBEAM;
		auto    cursorHandle = LoadCursor(NULL, cursor);
		SetClassLongPtr(GetHWnd(), GCLP_HCURSOR, reinterpret_cast<LPARAM>(cursorHandle));

		break;
	}
	case HXCursorStyle::ResizeNW: {
		LPCTSTR cursor       = IDC_SIZENWSE;
		auto    cursorHandle = LoadCursor(NULL, cursor);
		SetClassLongPtr(GetHWnd(), GCLP_HCURSOR, reinterpret_cast<LPARAM>(cursorHandle));

		break;
	}
	case HXCursorStyle::ResizeN: {
		LPCTSTR cursor       = IDC_SIZENS;
		auto    cursorHandle = LoadCursor(NULL, cursor);
		SetClassLongPtr(GetHWnd(), GCLP_HCURSOR, reinterpret_cast<LPARAM>(cursorHandle));

		break;
	}
	case HXCursorStyle::ResizeE: {
		LPCTSTR cursor       = IDC_SIZEWE;
		auto    cursorHandle = LoadCursor(NULL, cursor);
		SetClassLongPtr(GetHWnd(), GCLP_HCURSOR, reinterpret_cast<LPARAM>(cursorHandle));

		break;
	}
	case HXCursorStyle::ResizeES: {
		LPCTSTR cursor       = IDC_SIZENESW;
		auto    cursorHandle = LoadCursor(NULL, cursor);
		SetClassLongPtr(GetHWnd(), GCLP_HCURSOR, reinterpret_cast<LPARAM>(cursorHandle));

		break;
	}
	}
}

#ifdef UNICODE
bool HXOSOperationImpl::WriteClipboardText(const std::wstring &text)
#else
bool HXOSOperationImpl::WriteClipboardText(const std::string &text)
#endif
{
	if (!OpenClipboard(nullptr)) {
		DWORD error = GetLastError();
		return false;
	}

	EmptyClipboard();
	bool bSuccess = false;

#ifdef UNICODE
	const size_t byteSize = (text.size() + 1) * sizeof(wchar_t);
	HGLOBAL      hMem     = GlobalAlloc(GMEM_MOVEABLE, byteSize);
	if (hMem) {
		wchar_t *pMem = static_cast<wchar_t *>(GlobalLock(hMem));
		if (pMem) {
			wcscpy_s(pMem, text.size() + 1, text.c_str());
			GlobalUnlock(hMem);
			if (SetClipboardData(CF_UNICODETEXT, hMem)) {
				bSuccess = true;
			}
		}
		GlobalFree(hMem);
	}
#else
	const size_t         wsize = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, nullptr, 0);
	std::vector<wchar_t> wideBuffer(wsize);
	MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, wideBuffer.data(), static_cast<int>(wideBuffer.size()));

	const size_t unicodeByteSize = wsize * sizeof(wchar_t);
	HGLOBAL      hUnicodeMem     = GlobalAlloc(GMEM_MOVEABLE, unicodeByteSize);
	if (hUnicodeMem) {
		wchar_t *pUnicodeMem = static_cast<wchar_t *>(GlobalLock(hUnicodeMem));
		if (pUnicodeMem) {
			wcscpy_s(pUnicodeMem, wsize, wideBuffer.data());
			GlobalUnlock(hUnicodeMem);
			SetClipboardData(CF_UNICODETEXT, hUnicodeMem);
			bSuccess = true;
		}
		GlobalFree(hUnicodeMem);
	}
#endif

	CloseClipboard();
	return bSuccess;
}

void HXOSOperationImpl::CopyToClipboard(const HXString &String) {
	WriteClipboardText(String);
}

HXString HXOSOperationImpl::ReadFromClipboard() {
	return ReadClipboardText();
}

#ifdef UNICODE
std::wstring HXOSOperationImpl::ReadClipboardText()
#else
std::string HXOSOperationImpl::ReadClipboardText()
#endif
{
#ifdef UNICODE
	std::wstring result;
#else
	std::string result;
#endif

	if (!OpenClipboard(nullptr)) {
		return result;
	}

	UINT format    = 0;
	bool dataFound = false;

	while ((format = EnumClipboardFormats(format)) != 0 && !dataFound) {
		if (format == CF_UNICODETEXT) {
			HANDLE hData = GetClipboardData(CF_UNICODETEXT);
			if (hData) {
				if (wchar_t *wtext = static_cast<wchar_t *>(GlobalLock(hData))) {
					try {
#ifdef UNICODE
						result = wtext;
#else
						int utf8len = WideCharToMultiByte(
							CP_UTF8, 0, wtext, -1,
							nullptr, 0, nullptr, nullptr);

						if (utf8len > 0) {
							std::vector<char> buffer(utf8len);
							WideCharToMultiByte(
								CP_UTF8, 0, wtext, -1,
								buffer.data(), utf8len, nullptr, nullptr);
							result.assign(buffer.data());
						}
#endif
						dataFound = true;
					} catch (...) {
						GlobalUnlock(hData);
						CloseClipboard();
						throw;
					}
					GlobalUnlock(hData);
				}
			}
		} else if (format == CF_TEXT && !dataFound) {
#ifndef UNICODE
			HANDLE hData = GetClipboardData(CF_TEXT);
			if (hData) {
				if (char *text = static_cast<char *>(GlobalLock(hData))) {
					result    = text;
					dataFound = true;
					GlobalUnlock(hData);
				}
			}
#endif
		}
	}

	CloseClipboard();
	return result;
}
#undef line
